import { EventEmitter, Injectable } from '@angular/core';
import { Graph, Node } from '@antv/x6';
import * as signalR from "@microsoft/signalr";
import { Logger } from 'src/service/logger';
import { GraphConfigService, GraphOptionSetting } from '../graph-config.service';
import {
  FieldUpdatedEvent,
  GraphViewportSettings,
  ScenarioChangedEvent,
  ScenarioConfig,
  ScenarioLoadedEvent,
  ScenarioUnloadedEvent,
  SignalRConfig
} from '../models';
import { MonitorBoardNode } from '../nodes/MonitorBoardNode';
import { PiplineNode } from '../nodes/PiplineNode';
import { RgvNode } from '../nodes/RgvNode';
import { SiteNode } from '../nodes/SiteNode';
import { SrmNode } from '../nodes/SrmNode';
import { loadScenario, registerTypes, unregisterTypes } from '../nodes/nodes-util';

export declare const window: {
  __x6_instances__: any[];
} & Window;


@Injectable()
export class ScenarioMonitorGraphService {
  private connection!: signalR.HubConnection;
  nodeSelected: EventEmitter<Node> = new EventEmitter();
  private graph!: Graph;
  scenarioId?: string;

  constructor(
    config: SignalRConfig,
    private configService: GraphConfigService,
    private logger: Logger) {
    const connection =
      new signalR.HubConnectionBuilder()
        .withUrl(config.baseUrl)
        .withAutomaticReconnect({ nextRetryDelayInMilliseconds: () => 20000 })
        .build();
    connection.on("NotifyFieldUpdated", (e: FieldUpdatedEvent) => this.fieldValueUpdated(e));
    connection.on("NotifyScenarioChanged", (e: ScenarioChangedEvent) => this.onNotifyScenarioChanged(e));
    connection.on("NotifyScenarioLoaded", (e: ScenarioLoadedEvent) => this.onNotifyScenarioLoaded(e));
    connection.on("NotifyScenarioUnloaded", (e: ScenarioUnloadedEvent) => this.onNotifyScenarioUnloaded(e));

    connection.onreconnecting((err) => {
      logger.log("onreconnecting", err);
    });
    connection.onreconnected(async (connectionId) => {
      logger.log("onreconnected", connectionId);
      if (this.scenarioId) {
        await this.connection.send("Monitor", this.scenarioId);
        await this.connection?.send("Load", this.scenarioId);
      }
    });

    this.connection = connection;
  }

  onNotifyScenarioLoaded(e: ScenarioLoadedEvent) {

  }

  onNotifyScenarioUnloaded(e: ScenarioUnloadedEvent) {

  }

  onNotifyScenarioChanged(e: ScenarioChangedEvent) {

  }

  fieldValueUpdated(e: FieldUpdatedEvent) {
    const node = this.graph.getNodes().find(n => n.id == e.TargetId);
    if (node instanceof PiplineNode
      || node instanceof SiteNode
      || node instanceof SrmNode
      || node instanceof MonitorBoardNode
      || node instanceof RgvNode) {
      node.fieldValueUpdated(e);
    }
  }

  registerTypes() {
    registerTypes();
  }

  unregisterTypes() {
    unregisterTypes();
  }

  async UnMonitorScenario() {
    if (this.scenarioId) {
      await this.connection.send("UnMonitor", this.scenarioId);
    }
    await this.connection.stop();
  }

  async UnloadScenario() {
    if (this.scenarioId) {
      await this.connection.send("Unload", this.scenarioId);
      await this.connection.send("UnMonitor", this.scenarioId);
    }
  }

  async init(container: HTMLElement) {
    this.graph = new Graph({
      ... this.configService.getGraphOptions(this._getOptionSetting()),
      background: {
        image: '/assets/images/logo.png',
        size: '800px',
        position: 'bottom 10px left 10px',
        opacity: 0.02
      },
      moveThreshold: 10000,
      container: container,
      autoResize: container,
    });
    window.__x6_instances__ = [];
    window.__x6_instances__.push(this.graph);
    this.graph.on("node:added", ({ node, index, options }) => {

      if (node instanceof SiteNode
        || node instanceof SrmNode
        || node instanceof PiplineNode
        || node instanceof RgvNode
        || node instanceof MonitorBoardNode) {
        node.data.cfg.Id = node.id;
        node.setLogger(this.logger);
      }
    });
    this.setPlugins();
    this._graphBindKeys();
    await this.connection.start();
  }

  private _getOptionSetting(): Partial<GraphOptionSetting> {
    return {
      background: false,
      grid: true,
      panning: true,
      mousewheel: true,
    };
  }

  setPlugins() { }

  private _graphBindKeys() {
    const { graph } = this;
    graph.on("node:unselected", ({ node }) => {
      this.nodeSelected.emit(undefined);
    });

    graph.on("node:dblclick", ({ node }) => {
      this.nodeSelected.emit(node);
    });

    graph.on('scale', ({ sx, sy, ox, oy }) => {
      if (this.scenarioId) {
        const key = `View${this.scenarioId}`;
        const json = localStorage.getItem(key);
        const settings: GraphViewportSettings = {};
        settings.scale = { sx, sy, ox, oy };
        if (json) {
          const { translate } = JSON.parse(json) as GraphViewportSettings;
          settings.translate = translate;
        }
        localStorage.setItem(key, JSON.stringify(settings));
      }
    });

    graph.on('translate', ({ tx, ty }) => {
      if (this.scenarioId) {
        const key = `View${this.scenarioId}`;
        const json = localStorage.getItem(key);
        const settings: GraphViewportSettings = {};
        settings.translate = { tx, ty };
        if (json) {
          const { scale } = JSON.parse(json) as GraphViewportSettings;
          settings.scale = scale;
        }
        localStorage.setItem(key, JSON.stringify(settings));
      }
    });
  }

  async loadScenario(cfg: ScenarioConfig) {
    loadScenario(this.graph, cfg);
    this.scenarioId = cfg.Id;
    await this.connection.send("Monitor", this.scenarioId);
    await this.connection?.send("Load", this.scenarioId);
    this._loadViewport();
  }

  private _loadViewport() {
    if (this.scenarioId) {
      const key = `View${this.scenarioId}`;
      const json = localStorage.getItem(key);
      if (json) {
        const { scale, translate } = JSON.parse(json) as GraphViewportSettings;
        const { graph } = this;
        if (scale) {
          graph.scale(scale.sx, scale.sy, scale.ox, scale.oy);
        }
        if (translate) {
          graph.translate(translate.tx, translate.ty);
        }
      }
    }
  }

  clearViewport() {
    this.graph.scale(1, 1, 0, 0);
    this.graph.translate(0, 0);
    if (this.scenarioId) {
      const key = `View${this.scenarioId}`;
      localStorage.removeItem(key);
    }
  }

  async reload() {
    await this.connection.send("Reload", this.scenarioId);
  }

  updateSrmColumn(column: number) {
    const srm = this.graph.getNodes().find(a => a instanceof SrmNode);
    if (srm instanceof SrmNode) {
      srm.setColumn(column);
    }
  }
}
